package test.base.medium;

import java.io.File;
import java.io.IOException;

import javax.swing.JFrame;

import static org.bytedeco.opencv.global.opencv_highgui.imshow;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imread;
import org.bytedeco.ffmpeg.avcodec.AVCodec;
import org.bytedeco.ffmpeg.avcodec.AVCodecContext;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.avcodec.AVPicture;
import static org.opencv.core.CvType.*;
import static org.bytedeco.ffmpeg.global.avformat.*;
import static org.bytedeco.ffmpeg.global.avcodec.*;
import static org.bytedeco.ffmpeg.global.avutil.*;
import static org.bytedeco.ffmpeg.global.swscale.*;

import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.avformat.AVIOContext;
import org.bytedeco.ffmpeg.avformat.AVStream;
import org.bytedeco.ffmpeg.avutil.AVDictionary;
import org.bytedeco.ffmpeg.avutil.AVFrame;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.ffmpeg.swscale.SwsContext;
import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.javacpp.DoublePointer;
import org.bytedeco.javacpp.Pointer;
import org.bytedeco.javacpp.PointerPointer;
//import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.javacv.CanvasFrame;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.javacv.OpenCVFrameConverter.ToMat;
import org.bytedeco.opencv.global.opencv_imgcodecs;
import org.bytedeco.opencv.opencv_core.Mat;


public class ffmpegtest {
	

	public static void main(String [] arg) {
		ffmpegtest ff =new ffmpegtest
				();
		String rtspPath = "rtsp://admin:nt123321@192.168.0.2:554/h264/1/sub/av_stream";
		try {
		
			String msg=ff.openVideo(rtspPath);
			System.out.println(msg);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	//, String out_file
	private String openVideo(String url) throws IOException {
		AVFormatContext pFormatCtx = new AVFormatContext(null);
		int i, videoStream;
		AVCodecContext pCodecCtx = null;
		AVCodec pCodec = null;
		AVFrame pFrame = null;
		AVPacket packet = new AVPacket();
		int[] frameFinished = new int[1];
		AVDictionary optionsDict = null;
		int 图像格式 = AV_PIX_FMT_YUVJ420P;
		AVFrame pFrameRGB = null;
		int numBytes;
		BytePointer buffer = null;
		SwsContext sws_ctx = null;

		// 读取视频文件

		if (avformat_open_input(pFormatCtx, url, null, null) != 0) {
			return "无法打开"; // Couldn't open file

		}

		// 查找视频信息
		if (avformat_find_stream_info(pFormatCtx, (PointerPointer<Pointer>) null) < 0) {
			return "无法发现视频流信息";// Couldn't find stream information
		}
		/*
		 * 打印有关输入或输出格式的详细信息，例如<br> 持续时间，比特率，流，容器，程序，元数据，边数据，<br> 编解码器和时基。
		 */
		av_dump_format(pFormatCtx, 0, url, 0);// Dump information about file onto standard error

		// 发现关键帧
		videoStream = -1;
		for (i = 0; i < pFormatCtx.nb_streams(); i++) {
			if (pFormatCtx.streams(i).codec().codec_type() == AVMEDIA_TYPE_VIDEO) {
				videoStream = i;
				break;
			}
		}
		if (videoStream == -1) {
			return "找不到关键帧";
		}

		// 从关键帧 获取视频编码
		pCodecCtx = pFormatCtx.streams(videoStream).codec();

		// 查找解码器
		pCodec = avcodec_find_decoder(pCodecCtx.codec_id());
		if (pCodec == null) {
			System.err.println("Unsupported codec!");
			return "缺少解码器";
		}
		// 打开解码器
		if (avcodec_open2(pCodecCtx, pCodec, optionsDict) < 0) {
			return "无法打开解码器";
		}
		/*
		 * 分配一个AVFrame并将其字段设置为默认值。结果<br> 必须使用av_frame_free（）释放结构。<br>
		 * 
		 * @返回一个用默认值填充的AVFrame或失败时为NULL。<br> 
		 * 注意这只分配AVFrame本身，而不分配数据缓冲区。那些<br>
		 * 必须通过其他方式分配，例如使用av_frame_get_buffer（）或手动操作。<br>
		 */
		pFrame = av_frame_alloc();// Allocate video frame

		pFrameRGB = av_frame_alloc();
		if (pFrameRGB == null) {
			return "无法分配avframe";
		}
		int width = pCodecCtx.width(), height = pCodecCtx.height();
		pFrameRGB.width(width);
		pFrameRGB.height(height);
		pFrameRGB.format(图像格式);
		// 确定图像所需的缓冲区大小并分配缓冲区
		numBytes = avpicture_get_size(图像格式, width, height);
		/*
		 * 分配一个适合所有内存访问的对齐内存块（包括CPU上可用的向量）。
		 */
		buffer = new BytePointer(av_malloc(numBytes));
		/*
		 * 分配并返回一个SwsContext。你需要它来表演
		 * 
		 * 使用sws_scale（）进行缩放/转换操作。
		 */
		sws_ctx = sws_getContext(pCodecCtx.width(), pCodecCtx.height(), pCodecCtx.pix_fmt(), pCodecCtx.width(),
				pCodecCtx.height(), 图像格式, SWS_BICUBIC, null, null, (DoublePointer) null);

		// 将缓冲区的适当部分分配给pFrameRGB中的图像平面
		// 请注意，pFrameRGB是一个AVFrame，但AVFrame是一个AVPicture的超集
		//avpicture_fill(new AVPicture(pFrameRGB), buffer, 图像格式, pCodecCtx.width(), pCodecCtx.height());
		avutil.av_image_fill_arrays(pFrameRGB.data(), pFrameRGB.linesize(), buffer, 图像格式, pCodecCtx.width(), pCodecCtx.height(), 1) ;

		

	        // 新建canvasFrame窗口，命名
	        CanvasFrame canvasFrame = new CanvasFrame("测试点击");
	        // 关闭窗口则退出程序
	        canvasFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
	        canvasFrame.setAlwaysOnTop(true);
	     
	    	String outfile="temp.jpg";
		int ret = -1;
		  ToMat converter = new OpenCVFrameConverter.ToMat();
		// 读视频帧一个帧
		while (av_read_frame(pFormatCtx, packet) >= 0) {

			if (packet.stream_index() == videoStream) {// 如果是图像帧
				// 解码视频帧
				// 将AVPKT大小的视频帧从AVPKT>数据解码为图片。
				// 一些解码器可以支持单个av包中的多个帧，例如解码器将只解码第一帧。
				avcodec_decode_video2(pCodecCtx, pFrame, frameFinished, packet);

				// 如果获取到完整帧,就是关键帧
				if (frameFinished != null) {
					// 转换图像格式，将解压出来的YUV420P的图像转换为YUVJ420P的图像
					sws_scale(sws_ctx, pFrame.data(), pFrame.linesize(), 0, pCodecCtx.height(), pFrameRGB.data(),
							pFrameRGB.linesize());
				}
				// 如果是b p 帧
				if (frameFinished[0] != 0 && !pFrame.isNull()) {
					// 转换图像从 native format 到 YUVJ420P
					sws_scale(sws_ctx, pFrame.data(), pFrame.linesize(), 0, pCodecCtx.height(), pFrameRGB.data(),
							pFrameRGB.linesize());
					//System.out.println(pFrameRGB);
				
					if ((ret = saveImg(pFrameRGB,outfile)) >= 0) {
						  Mat mat = opencv_imgcodecs.imread(outfile);
							break;
				
					      //  canvasFrame.showImage(converter.convert(mat));
					}	 
				}
			}

		}
		//手动释放内存
		av_packet_unref(packet);// Free the packet that was allocated by av_read_frame

		av_free(buffer);

		av_free(pFrameRGB);

		av_free(pFrame);// Free the YUV frame
		//关闭解码器
		avcodec_close(pCodecCtx);// Close the codec
		//关闭图像源
		avformat_close_input(pFormatCtx);// Close the video file
	
		return "完毕";
	}


	private int saveImg(AVFrame pFrame, String outfile) {//
		
	//	final File outfile = File.createTempFile("temp", ".html");
	
		av_log_set_level(AV_LOG_ERROR);// 设置FFmpeg日志级别（默认是debug，设置成error可以屏蔽大多数不必要的控制台消息）
		AVPacket pkt = null;
		AVStream pAVStream = null;
		AVCodec codec = null;
		int ret = -1;

		int width = pFrame.width(), height = pFrame.height();
		// 分配AVFormatContext  这是ffmpeg的根类
		AVFormatContext pFormatCtx = avformat_alloc_context();
		// 设置输出文件格式
		pFormatCtx.oformat(av_guess_format("mjpeg", null, null));
		if (pFormatCtx.oformat() == null) {
			return -1;
		}
		try {
			//管理输入输出的
			// 创建并初始化一个和该url相关的AVIOContext
			AVIOContext pb = new AVIOContext();
			if (avio_open(pb, outfile, AVIO_FLAG_READ_WRITE) < 0) {// dont open file
				return -1;
			}
			pFormatCtx.pb(pb);
			// 构建一个新stream
			pAVStream = avformat_new_stream(pFormatCtx, codec);
			if (pAVStream == null) {
				return -1;
			}
			int codec_id = pFormatCtx.oformat().video_codec();
			// 设置该stream的信息
		
			AVCodecContext pCodecCtx = pAVStream.codec();
			pCodecCtx.codec_id(codec_id);
			pCodecCtx.codec_type(AVMEDIA_TYPE_VIDEO);
			pCodecCtx.pix_fmt(AV_PIX_FMT_YUVJ420P);
			pCodecCtx.width(width);
			pCodecCtx.height(height);
			pCodecCtx.time_base().num(1);
			pCodecCtx.time_base().den(25);

			// 设置输出文件
			av_dump_format(pFormatCtx, 0, outfile, 1);
	
			// 查找解码器
			AVCodec pCodec = avcodec_find_encoder(codec_id);
			if (pCodec == null) {// codec not found
				return -1;
			}
			// 设置pCodecCtx的解码器为pCodec
			if (avcodec_open2(pCodecCtx, pCodec, (PointerPointer<Pointer>) null) < 0) {
				System.err.println("不能打开解码器");
				return -1;
			}

			// 把图像信息写入头部
			avformat_write_header(pFormatCtx, (PointerPointer<Pointer>) null);

			// 给AVPacket分配足够大的空间
			pkt = new AVPacket();
			if (av_new_packet(pkt, width * height * 3) < 0) {
				return -1;
			}
			int[] got_picture = { 0 };
			// 编码
			if (avcodec_encode_video2(pCodecCtx, pkt, pFrame, got_picture) >= 0) {
				// flush
				if ((ret = av_write_frame(pFormatCtx, pkt)) >= 0) {
					// Write 预告片
					if (av_write_trailer(pFormatCtx) >= 0) {
						//System.err.println("Encode Successful.");
					}
				}
			}
			return ret;
			// 结束时销毁
		} finally {
			if (pkt != null) {
				av_packet_unref(pkt);
			}
			if (pAVStream != null) {
				avcodec_close(pAVStream.codec());
			}
			if (pFormatCtx != null) {
				avio_close(pFormatCtx.pb());
				avformat_free_context(pFormatCtx);
			}
		}
	}
}
